package com.lkd.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Strings;
import com.lkd.common.VMSystem;
import com.lkd.config.TopicConfig;
import com.lkd.contract.SupplyChannel;
import com.lkd.contract.SupplyContract;
import com.lkd.contract.TaskCompleteContract;
import com.lkd.dao.TaskDao;
import com.lkd.dao.TaskReportDao;
import com.lkd.emq.MqttProducer;
import com.lkd.entity.TaskDetailsEntity;
import com.lkd.entity.TaskEntity;
import com.lkd.entity.TaskStatusTypeEntity;
import com.lkd.exception.LogicException;
import com.lkd.feign.UserService;
import com.lkd.feign.VMService;
import com.lkd.http.vo.CancelTaskViewModel;
import com.lkd.http.vo.TaskDetailsViewModel;
import com.lkd.http.vo.TaskReportInfoVO;
import com.lkd.http.vo.TaskViewModel;
import com.lkd.service.TaskDetailsService;
import com.lkd.service.TaskService;
import com.lkd.service.TaskStatusTypeService;
import com.lkd.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.jni.Local;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.stream.Task;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
public class TaskServiceImpl extends ServiceImpl<TaskDao,TaskEntity> implements TaskService{

    @Autowired
    private TaskStatusTypeService statusTypeService;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private VMService vmService;

    @Autowired
    private UserService userService;

    @Autowired
    private TaskDetailsService taskDetailsService;

    @Autowired
    private MqttProducer mqttProducer;

    @Autowired
    private TaskReportDao taskReportDao;

    @Override
    public Pager<TaskEntity> search(Long pageIndex, Long pageSize, String innerCode, Integer userId, String taskCode, Integer status, Boolean isRepair, String start, String end) {



        Page<TaskEntity> page = new Page<>(pageIndex,pageSize);
        LambdaQueryWrapper<TaskEntity> qw = new LambdaQueryWrapper<>();
        if(!Strings.isNullOrEmpty(innerCode)){
            qw.eq(TaskEntity::getInnerCode,innerCode);
        }
        if(userId != null && userId > 0){
            qw.eq(TaskEntity::getUserId,userId);
        }
        if(!Strings.isNullOrEmpty(taskCode)){
            qw.like(TaskEntity::getTaskCode,taskCode);
        }
        if(status != null && status > 0){
            qw.eq(TaskEntity::getTaskStatus,status);
        }
        if(isRepair != null){
            if(isRepair){
                qw.ne(TaskEntity::getProductTypeId, VMSystem.TASK_TYPE_SUPPLY);
            }else {
                qw.eq(TaskEntity::getProductTypeId,VMSystem.TASK_TYPE_SUPPLY);
            }
        }
        if(!Strings.isNullOrEmpty(start) && !Strings.isNullOrEmpty(end)){
            qw
                    .ge(TaskEntity::getCreateTime, LocalDate.parse(start, DateTimeFormatter.ISO_LOCAL_DATE))
                    .le(TaskEntity::getCreateTime,LocalDate.parse(end,DateTimeFormatter.ISO_LOCAL_DATE));
        }
        //根据最后更新时间倒序排序
        qw.orderByDesc(TaskEntity::getUpdateTime);

        return Pager.build(this.page(page,qw));
    }



    @Override
    public List<TaskStatusTypeEntity> getAllStatus() {
        QueryWrapper<TaskStatusTypeEntity> qw = new QueryWrapper<>();
        qw.lambda()
                .ge(TaskStatusTypeEntity::getStatusId,VMSystem.TASK_STATUS_CREATE);

        return statusTypeService.list(qw);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean create(TaskViewModel taskViewModel) {

        String innerCode = taskViewModel.getInnerCode();
        int taskType = taskViewModel.getProductType();

        //1.校验当前售货机是否有未完成的工单
        checkUndoTask(innerCode,taskType);

        //2.feign接口调用，获取售货机信息
        VmVO vmInfo = vmService.getVMInfo(innerCode);
        if(vmInfo == null){
            throw new LogicException("售货机不存在!");
        }

        //3.判断售货机状态
        /**
         * （1）如果是投放工单，状态为运营，抛出异常
         * （2）如果是补货工单，状态不是运营状态，抛出异常
         * （3）如果是撤机工单，状态不是运营状态，抛出异常
         */
        Integer vmStatus = vmInfo.getVmStatus();
        checkVmStatusAndTaskType(taskType, vmStatus);
        //4.按照年月日+4位当日序号生成工单编号
        TaskEntity taskEntity = new TaskEntity();
        String code = generateTaskCode();
        taskEntity.setTaskCode(code);
        BeanUtils.copyProperties(taskViewModel,taskEntity);
        //设置特殊字段
        taskEntity.setProductTypeId(taskType);

        //售货机字段设置
        taskEntity.setAddr(vmInfo.getNodeAddr());
        taskEntity.setRegionId(vmInfo.getRegionId());
        //初始化工单状态
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_CREATE);

        //5.调用user微服务获取当前用户信息
        UserVO user = userService.getUser(taskViewModel.getUserId());
        if(user == null){
            throw new LogicException("用户不存在!");
        }
        taskEntity.setUserName(user.getUserName());

        //6.在任务表中插入数据
        save(taskEntity);

        //处理补货信息
        if(Objects.equals(taskType,VMSystem.TASK_TYPE_SUPPLY)){
            List<TaskDetailsViewModel> details = taskViewModel.getDetails();
            if(CollectionUtils.isEmpty(details)){
                throw new LogicException("补货详情不能为空!");
            }
//            List<TaskDetailsEntity> taskDetailsEntities = new ArrayList<>();
//            for (TaskDetailsViewModel detail : details) {
//                TaskDetailsEntity taskDetailsEntity = new TaskDetailsEntity();
//                BeanUtils.copyProperties(detail,taskDetailsEntity);
//                taskDetailsEntity.setTaskId(taskEntity.getTaskId());
//                taskDetailsEntities.add(taskDetailsEntity);
//            }
//            taskDetailsService.saveBatch(taskDetailsEntities);
            //stream处理
            List<TaskDetailsEntity> list = details.stream().map(x -> {
                TaskDetailsEntity taskDetailsEntity = new TaskDetailsEntity();
                BeanUtils.copyProperties(x, taskDetailsEntity);
                taskDetailsEntity.setTaskId(taskEntity.getTaskId());
                return taskDetailsEntity;
            }).collect(Collectors.toList());
            taskDetailsService.saveBatch(list);
        }

        taskCountModify(user,1);

        return true;
    }

    private void checkVmStatusAndTaskType(int taskType, Integer vmStatus) {
        if(Objects.equals(taskType,VMSystem.TASK_TYPE_DEPLOY) && Objects.equals(vmStatus,VMSystem.VM_STATUS_RUNNING)){
            //（1）如果是投放工单，状态为运营，抛出异常
            throw new LogicException("运营状态的售货机不能创建投放工单!");
        }
        if(Objects.equals(taskType,VMSystem.TASK_TYPE_SUPPLY) && !Objects.equals(vmStatus,VMSystem.VM_STATUS_RUNNING)){
            //（2）如果是补货工单，状态不是运营状态，抛出异常
            throw new LogicException("售货机状态和工单类型不匹配!");
        }
        if(Objects.equals(taskType,VMSystem.TASK_TYPE_REVOKE) && !Objects.equals(vmStatus,VMSystem.VM_STATUS_RUNNING)){
            //（3）如果是撤机工单，状态不为运营，抛出异常
            throw new LogicException("售货机状态和工单类型不匹配!");
        }
    }

    //是否存在未完成的工单
    private void checkUndoTask(String innerCode,int taskType) {
        LambdaQueryWrapper<TaskEntity> wrapper = Wrappers.<TaskEntity>lambdaQuery().eq(TaskEntity::getInnerCode, innerCode)
                .eq(TaskEntity::getProductTypeId, taskType)
                .le(TaskEntity::getTaskStatus, VMSystem.TASK_STATUS_PROGRESS);
        int count = count(wrapper);
        if(count > 0){
            throw new LogicException("存在未完成的工单!");
        }
    }

    private void taskCountModify(int userId,int add){
        UserVO user = userService.getUser(userId);
        taskCountModify(user,add);
    }

    private void taskCountModify(UserVO user,int add){
        String key = VMSystem.generateUserTaskCountKey(user.getRegionId(), user.getRoleCode());
        // zset中的用户分值 加1 或 减1操作
        redisTemplate.opsForZSet().incrementScore(key,user.getUserId(),add);
    }

    /**
     * 生成工单编号
     * @return
     */
    private String generateTaskCode(){
        //日期+序号
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));  //日期字符串 20220818  0002
        String key= "lkd.task.code."+date; //redis key
        Object obj = redisTemplate.opsForValue().get(key);
        if(obj==null){
            redisTemplate.opsForValue().set(key,1L, Duration.ofDays(1) );
            return date+"0001";
        }
        return date+  Strings.padStart( redisTemplate.opsForValue().increment(key,1).toString(),4,'0');
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean accept(TaskEntity task) {
        if(!Objects.equals(task.getTaskStatus(), VMSystem.TASK_STATUS_CREATE)){
            throw new LogicException("工单状态不是待处理");
        }
        task.setTaskStatus( VMSystem.TASK_STATUS_PROGRESS );//修改工单状态为进行
        return this.updateById(task);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean cancelTask(TaskEntity task, CancelTaskViewModel cancelVM) {
        if(Objects.equals(task.getTaskStatus(), VMSystem.TASK_STATUS_FINISH) ||
                Objects.equals(task.getTaskStatus(), VMSystem.TASK_STATUS_CANCEL)){
            throw new LogicException("工单已经结束");
        }

        task.setTaskStatus( VMSystem.TASK_STATUS_CANCEL  );
        task.setDesc(cancelVM.getDesc());
        boolean b = this.updateById(task);
        if(!b){
            return false;
        }

        //减1
        taskCountModify(task.getUserId(),-1);

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean completeTask(TaskEntity taskEntity) {
        if(Objects.equals(taskEntity.getTaskStatus(), VMSystem.TASK_STATUS_FINISH)
                || Objects.equals(taskEntity.getTaskStatus(), VMSystem.TASK_STATUS_CANCEL)){
            throw new LogicException("工单已经结束");
        }

        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_FINISH);//修改工单状态
        this.updateById(taskEntity);

        //1.判断是否是运维工单 投放、撤机
        if (Objects.equals(taskEntity.getProductTypeId(), VMSystem.TASK_TYPE_DEPLOY)
                || Objects.equals(taskEntity.getProductTypeId(), VMSystem.TASK_TYPE_REVOKE)
        ) {
            notifyOperationTask(taskEntity.getInnerCode(),taskEntity.getProductTypeId());
        }

        //2.判断是否是补货工单
        else if (Objects.equals(taskEntity.getProductTypeId(), VMSystem.TASK_TYPE_SUPPLY)
        ) {
            notifySupplyTask(taskEntity.getInnerCode(),taskEntity.getTaskId());
        }

        //减1
        taskCountModify(taskEntity.getUserId(),-1);

        return true;
    }

    @Override
    public int getLeastUser(long regionId, boolean isRepair) {
        String key = VMSystem.generateUserTaskCountKey(regionId,isRepair ? "1003" : "1002");
        Set<Object> range = redisTemplate.opsForZSet().range(key, 0, 0);
        if(CollectionUtils.isEmpty(range)){
            return 0;
        }
        return (Integer) new ArrayList<>(range).get(0);
    }

    @Override
    public List<TaskReportInfoVO> taskReportInfo(LocalDateTime start, LocalDateTime end) {

        //获取当前年月日
        String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

        List<TaskReportInfoVO> taskReportInfoVOS = new ArrayList<>();

        TaskReportInfoVO supplyVo = new TaskReportInfoVO();
        supplyVo.setDate(date);
        supplyVo.setRepair(false);
        supplyVo.setWorkerCount(userService.getOperatorCount());
        supplyVo.setProgressTotal(taskReportDao.selectTaskReport(VMSystem.TASK_STATUS_PROGRESS,1,start,end));
        supplyVo.setCompletedTotal(taskReportDao.selectTaskReport(VMSystem.TASK_STATUS_FINISH,1,start,end));
        supplyVo.setCancelTotal(taskReportDao.selectTaskReport(VMSystem.TASK_STATUS_CANCEL,1,start,end));
        supplyVo.setTotal(taskReportDao.selectTaskReport(null,1,start,end));

        taskReportInfoVOS.add(supplyVo);

        TaskReportInfoVO repiarVo = new TaskReportInfoVO();
        repiarVo.setDate(date);
        repiarVo.setRepair(true);
        repiarVo.setWorkerCount(userService.getRepairerCount());
        repiarVo.setProgressTotal(taskReportDao.selectTaskReport(VMSystem.TASK_STATUS_PROGRESS,2,start,end));
        repiarVo.setCompletedTotal(taskReportDao.selectTaskReport(VMSystem.TASK_STATUS_FINISH,2,start,end));
        repiarVo.setCancelTotal(taskReportDao.selectTaskReport(VMSystem.TASK_STATUS_CANCEL,2,start,end));
        repiarVo.setTotal(taskReportDao.selectTaskReport(null,2,start,end));

        taskReportInfoVOS.add(repiarVo);

        return taskReportInfoVOS;
    }

    @Override
    public List<UserWorkVO> userWorkTop10(LocalDate start, LocalDate end, Boolean isRepair, Long regionId) {

        //获取明天的日期
        end = end.plusDays(1);

        Page<UserWorkVO> page = new Page<>();
        page.setCurrent(1);
        page.setSize(10);
        page.setSearchCount(false);

        return taskReportDao.userWorkTop10(page, start, end, isRepair ? 2 : 1, regionId).getRecords();
    }

    private int collectDateReport(LocalDate date,int status){
        return this.count(Wrappers.<TaskEntity>lambdaQuery()
                .ge(TaskEntity::getUpdateTime, date)
                .lt(TaskEntity::getUpdateTime, date.plusDays(1))
                .eq(TaskEntity::getTaskStatus, status)
        );
    }

    @Override
    public List<CollectReportVO> collectReport(LocalDate start, LocalDate end) {

        List<CollectReportVO> vos = new ArrayList<>();

        start.datesUntil(end.plusDays(1), Period.ofDays(1))
                .forEach(date -> {
                    CollectReportVO collectReportVO = new CollectReportVO();
                    //对vo进行赋值
                    collectReportVO.setCollectDate(date);
                    collectReportVO.setCancelCount(collectDateReport(date,VMSystem.TASK_STATUS_CANCEL));
                    collectReportVO.setFinishCount(collectDateReport(date,VMSystem.TASK_STATUS_FINISH));
                    collectReportVO.setProgressCount(collectDateReport(date,VMSystem.TASK_STATUS_PROGRESS));

                    vos.add(collectReportVO);
                });

        return vos;
    }

    private void notifySupplyTask(String innerCode, Long taskId) {
        //创建消息对象
        SupplyContract supplyContract = new SupplyContract();
        supplyContract.setInnerCode(innerCode);

        //查询详情信息
        List<TaskDetailsEntity> list = taskDetailsService.list(Wrappers.<TaskDetailsEntity>lambdaQuery()
                .eq(TaskDetailsEntity::getTaskId, taskId)
        );
        if(CollectionUtils.isEmpty(list)){
            return;
        }

        //设置补货数据
        //SupplyChannel
        List<SupplyChannel> supplyChannels = list.stream().map(x -> {
            SupplyChannel supplyChannel = new SupplyChannel();
            BeanUtils.copyProperties(x, supplyChannel);
            supplyChannel.setChannelId(x.getChannelCode());
            supplyChannel.setCapacity(x.getExpectCapacity());
            return supplyChannel;
        }).collect(Collectors.toList());

        //将集合设置到协议对象中
        supplyContract.setSupplyData(supplyChannels);

        try {
            mqttProducer.send(TopicConfig.VMS_SUPPLY_TOPIC ,1,supplyContract);
        } catch (JsonProcessingException e) {
            log.error("补货工单完成发送消息序列化错误",e);
            throw new LogicException("补货工单完成发送消息序列化错误");
        }
    }

    private void notifyOperationTask(String innerCode,int taskType) {
        TaskCompleteContract contract = new TaskCompleteContract();
        contract.setInnerCode(innerCode);
        contract.setTaskType(taskType);

        try {
            mqttProducer.send(TopicConfig.VMS_COMPLETED_TOPIC,1,contract);
        } catch (JsonProcessingException e) {
            log.error("运维工单完成发送消息序列化错误",e);
            throw new LogicException("运维工单完成发送消息序列化错误");
        }
    }
}
